// Copyright (C) 2024 Kumo inc.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#include <kthread/internal/types.h>
#include <kthread/internal/kutex.h>

kthread_once_t::kthread_once_t()
    : _kutex(kthread::kutex_create_checked<std::atomic<int>>())  {
    _kutex->store(UNINITIALIZED, std::memory_order_relaxed);
}

kthread_once_t::~kthread_once_t() {
    kthread::kutex_destroy(_kutex);
}

namespace kthread {

int kthread_once_impl(kthread_once_t* once_control, void (*init_routine)()) {
    std::atomic<int>* kutex = once_control->_kutex;
    // We need acquire memory order for this load because if the value
    // signals that initialization has finished, we need to see any
    // data modifications done during initialization.
    int val = kutex->load(std::memory_order_acquire);
    if (TURBO_LIKELY(val == kthread_once_t::INITIALIZED)) {
        // The initialization has already been done.
        return 0;
    }
    val = kthread_once_t::UNINITIALIZED;
    if (kutex->compare_exchange_strong(val, kthread_once_t::INPROGRESS,
                                       std::memory_order_relaxed,
                                       std::memory_order_relaxed)) {
        // This (b)thread is the first and the Only one here. Do the initialization.
        init_routine();
        // Mark *once_control as having finished the initialization. We need
        // release memory order here because we need to synchronize with other
        // (b)threads that want to use the initialized data.
        kutex->store(kthread_once_t::INITIALIZED, std::memory_order_release);
        // Wake up all other (b)threads.
        kthread::kutex_wake_all(kutex);
        return 0;
    }

    while (true) {
        // Same as above, we need acquire memory order.
        val = kutex->load(std::memory_order_acquire);
        if (TURBO_LIKELY(val == kthread_once_t::INITIALIZED)) {
            // The initialization has already been done.
            return 0;
        }
        // Unless your constructor can be very time consuming, it is very unlikely o hit
        // this race. When it does, we just wait the thread until the object has been created.
        if (kthread::kutex_wait(kutex, val, nullptr) < 0 &&
            errno != EWOULDBLOCK && errno != EINTR/*note*/) {
            return errno;
        }
    }
}

} // namespace kthread

__BEGIN_DECLS

int kthread_once(kthread_once_t* once_control, void (*init_routine)()) {
    return kthread::kthread_once_impl(once_control, init_routine);
}

__END_DECLS